import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Hashtable;

import javax.imageio.ImageIO;

import uchicago.src.reflector.ListPropertyDescriptor;
import uchicago.src.sim.analysis.OpenSequenceGraph;
import uchicago.src.sim.analysis.Sequence;
import uchicago.src.sim.engine.BasicAction;
import uchicago.src.sim.engine.Schedule;
import uchicago.src.sim.engine.SimInit;
import uchicago.src.sim.engine.SimModelImpl;
import uchicago.src.sim.gui.DisplaySurface;
import uchicago.src.sim.gui.Object2DDisplay;
import uchicago.src.sim.space.Object2DGrid;
import uchicago.src.sim.space.Object2DTorus;

public class RFSModel extends SimModelImpl {

	class MainAction extends BasicAction {

		@Override
		public void execute() {
			
			if (commander.noMoreFire()) {
				showStatistics();
				stop();
			}
							
			for (Firefighter firefighter : firefighters) {									
				commander.action(firefighter);									
				firefighter.action();			
			}
			
			for (Cell c : commander.getBurningCells()) {			
				terrain.spread(c);
			}		
		}
	}
	
	public void showStatistics() {
		
		int ndead = 0, ninjured = 0;
		for (Firefighter f : firefighters) {
			if (f.isDead()) ndead++;
			else if (f.isInjured()) ninjured++;
		}
		
		int nburnt = 0, nout = 0, nnormal = 0;
		for (int x = 0; x < space.getSizeX(); x++) 
			for (int y = 0; y < space.getSizeY(); y++) {
				Cell c = (Cell) space.getObjectAt(x, y);
				if (c.isBurnt()) nburnt++;
				else if (c.isOut()) nout++;
				else if (c.isNormal()) nnormal++;
			}
		
		System.out.println("ticks: " + (int)getTickCount());
		System.out.println("dead: " + ndead);
		System.out.println("injured: " + ninjured);
		System.out.println("normal: " + (nFirefighters - (ndead+ninjured)));					
		System.out.println("burnt: " + nburnt);
		System.out.println("out: " + nout);
		System.out.println("normal: " + nnormal);		
	}
	
	public static void main(String[] args) {		
		SimInit init = new SimInit();
		init.loadModel(new RFSModel(), null, false);
	}
	
	private Schedule schedule;
	private DisplaySurface dsurf;
	private Object2DTorus space;
	private OpenSequenceGraph plot_terrain;
	private OpenSequenceGraph plot_firefighters;
	private ArrayList<Firefighter> firefighters;
	private int nFirefighters, spaceSize;
	private Terrain terrain;
	private int[][] map;
	private String sWind;
	public static ArrayList<Color> mapColors = new ArrayList<Color>();
	private int windSpeed;
	private static Commander commander;
	private boolean firebreakFlag;
	private int iWindDirection;
	private Terrain.Wind windDirection;
	private int iPositioning;
	private int iIntelligence;
	private boolean firebreak = false;
	private int fireOriginX;
	private int fireOriginY;
	
	public RFSModel() {
		this.nFirefighters = 5;
		this.spaceSize = 100;
		this.sWind = "N";
		this.windSpeed = 20;
		this.firebreak = true;
		this.iPositioning = 1;
		this.iIntelligence = 1;
		this.iWindDirection = 1;
		this.windDirection = Terrain.Wind.N;
		this.fireOriginX = 60;
		this.fireOriginY = 40;
	}
	
	@Override
	public void begin() {
		try {
			buildModel();
		} catch (IOException e) {
			System.err.println("erro a construir o modelo"); 
		}
		buildDisplay();
		buildSchedule();
	}
	
	private void buildDisplay() {

		// display terrain
		Object2DGrid spaceCell = new Object2DGrid(100, 100);
		Object2DDisplay displayCell = new Object2DDisplay(spaceCell);
		displayCell.setObjectList(terrain.getCells());
		dsurf.addDisplayable(displayCell, "Cell Space");

		// display firefighters
		Object2DDisplay display = new Object2DDisplay(space);
		display.setObjectList(firefighters);
		dsurf.addDisplayable(display, "Agents Space");

		dsurf.display();

		// graph
		if (plot_terrain != null) {
			plot_terrain.dispose();
		}
		plot_terrain = new OpenSequenceGraph("Terrain Statistics", this);
		plot_terrain.setAxisTitles("time", "n");
		// plot number of different existing colors
		plot_terrain.addSequence("Burning Cells", new Sequence() {
			@Override
			public double getSValue() {
				return commander.getBurningCells().size();
			}
		});
		plot_terrain.addSequence("Burnt Cells", new Sequence() {

			@Override
			public double getSValue() {
				return commander.getBurntCells().size();
			}
		});
		
		plot_terrain.addSequence("Fought Cells", new Sequence() {

			@Override
			public double getSValue() {
				return terrain.getNumberOutCells();
			}
		});
				
		plot_firefighters = new OpenSequenceGraph("Firefighter Statistics", this);
		plot_firefighters.setAxisTitles("time", "n");
		plot_firefighters.addSequence("Died", new Sequence() {
			public double getSValue() {
				int ret = 0;
				for (int i = 0; i < firefighters.size(); i++)
					if (firefighters.get(i).isDead())
						ret++;
				return ret;
			}
		});
		
		plot_firefighters.addSequence("Injured", new Sequence() {
			public double getSValue() {
				int ret = 0;
				for (int i = 0; i < firefighters.size(); i++)
					if (firefighters.get(i).isInjured())
						ret++;
				return ret;
			}
		});
		
		plot_firefighters.display();
		plot_terrain.display();
		
	}
	
	public int getFireOriginX() {
		return fireOriginX;
	}
	
	public int getFireOriginY() {
		return fireOriginY;
	}
	
	public void setFireOriginX(int x) {
		this.fireOriginX = x;
	}
	
	public void setFireOriginY(int y) {
		this.fireOriginY = y;
	}
		
	public void buildModel() throws IOException {
		
		space = new Object2DTorus(spaceSize, spaceSize);

		buildTerrain();

		terrain.getCell(fireOriginX, fireOriginY).setOnFire();
			
		commander = new Commander(new Point(0,0), iPositioning, iIntelligence, space, sWind, nFirefighters);

		if (firebreak && iWindDirection != 0) firebreakFlag = true;
		
		Firefighter.setWind(Terrain.getSWind(iWindDirection));

		firefighters = new ArrayList<Firefighter>();
		for (int i = 0; i < nFirefighters; i++) {			
			Firefighter f = new Firefighter(new Point(i, 0), space);			
			if (firebreakFlag) {
				f.setFirebreak(true);
				firebreakFlag = false;	
			}			
			firefighters.add(f);
		}

		for (int i = 0; i < firefighters.size(); i++)
			commander.firstAction(firefighters.get(i), i);

	}
	
	public int getIntelligence() {
		return iIntelligence;
	}
	
	public void setIntelligence(int i) {
		iIntelligence = i;		
	}
	
	public String getSWind() {
		if (iWindDirection == 0) return null;
		else if (iWindDirection == 1) return "N";
		else if (iWindDirection == 2) return "NE";
		else if (iWindDirection == 3) return "E";
		else if (iWindDirection == 4) return "SE";
		else if (iWindDirection == 5) return "S";
		else if (iWindDirection == 6) return "SW";
		else if (iWindDirection == 7) return "W";
		else return "NW";
	}
	
    private void buildSchedule() {
		schedule.scheduleActionBeginning(0, new MainAction());
		schedule.scheduleActionAtInterval(1, dsurf, "updateDisplay", Schedule.LAST);
		schedule.scheduleActionAtInterval(1, plot_terrain, "step", Schedule.LAST);
		schedule.scheduleActionAtInterval(1, plot_firefighters, "step", Schedule.LAST);
	}
	
	public void buildTerrain() {

		ArrayList<Cell> cells = new ArrayList<Cell>();
		Cell cell;
		
		for (int x = 0; x < space.getSizeX(); x++) {
			for (int y = 0; y < space.getSizeY(); y++) {

				int r = map[x][y];
				
				if (r == 0) 
					cell = new Cell(new Point(x,y), Cell.Density.none, Cell.State.normal);
				else if (r == 2)
					cell = new Cell(new Point(x,y), Cell.Density.low, Cell.State.normal);
				else if (r == 3)
					cell = new Cell(new Point(x,y), Cell.Density.medium, Cell.State.normal);
				else
					cell = new Cell(new Point(x,y), Cell.Density.high, Cell.State.normal);

				space.putObjectAt(x, y, cell);

				cells.add(cell);
			}
		}
		
		terrain = new Terrain(cells, windDirection, this.getWindSpeed());
		
	}
	
	public boolean getFirebreak() {
		return firebreak;
	}

	@Override
	public String[] getInitParam() {
		return new String[] { "Firefighters", "WindDirection", "WindSpeed", "Positioning", "Intelligence", "Firebreak", "FireOriginX", "FireOriginY" };
	}

	@Override
	public String getName() {
		return "Repast Firefighting Simulation";
	}

	public int getFirefighters() {
		return nFirefighters;
	}
	
	public void setWindDirection(int iWind) {
        
		iWindDirection = iWind;
		
		if (iWindDirection == 0) windDirection = null;
		else if (iWindDirection == 1) windDirection = Terrain.Wind.N;
        else if (iWindDirection == 2) windDirection = Terrain.Wind.NE;
        else if (iWindDirection == 3) windDirection = Terrain.Wind.E;
        else if (iWindDirection == 4) windDirection = Terrain.Wind.SE;
        else if (iWindDirection == 5) windDirection = Terrain.Wind.S;
        else if (iWindDirection == 6) windDirection = Terrain.Wind.SW;
        else if (iWindDirection == 7) windDirection = Terrain.Wind.W;
        else windDirection = Terrain.Wind.NW; 
		
		sWind = getSWind();
    }

	@SuppressWarnings("unchecked")
    public Hashtable<String, ListPropertyDescriptor> getParameterDescriptors() {        
		
		// direccao vento
        Hashtable<Integer, String> wind = new Hashtable<Integer, String>();
        wind.put(0, "None");
        wind.put(1, "N");
        wind.put(2, "NE");        
        wind.put(3, "E");
        wind.put(4, "SE");
        wind.put(5, "S");
        wind.put(6, "SW");        
        wind.put(7, "W");
        wind.put(8, "NW");

        ListPropertyDescriptor pd = new ListPropertyDescriptor("WindDirection", wind);
        descriptors.put("WindDirection", pd);
        
        // posicionamento
        Hashtable<Integer, String> positioning = new Hashtable<Integer, String>();        
        positioning.put(1, "Front");
        positioning.put(2, "Back");        
        positioning.put(3, "Mixed");
                
        ListPropertyDescriptor pd1 = new ListPropertyDescriptor("Positioning", positioning);
        descriptors.put("Positioning", pd1);
        
        // inteligencia
        Hashtable<Integer, String> intelligence = new Hashtable<Integer, String>();        
        intelligence.put(1, "Near");
        intelligence.put(2, "Recent");        

        ListPropertyDescriptor pd2 = new ListPropertyDescriptor("Intelligence", intelligence);
        descriptors.put("Intelligence", pd2);
                        
        return descriptors;
    }

	@Override
	public Schedule getSchedule() {
		return schedule;
	}

	public int getSpaceSize() {
		return spaceSize;
	}

	public int getPositioning() {
        return iPositioning;
    }
	
	public int getWindDirection() {
		return iWindDirection;
	}

	public int getWindSpeed() {
		return windSpeed;
	}

	public int[][] importMap() throws IOException {

		// File file = new File(System.getProperty("user.home") + "/Desktop/" + "map.png");
		
		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		URL url = classLoader.getResource("map3.png");
		File file = new File(url.getPath());

		BufferedImage image = ImageIO.read(file);
		int[][] mat = new int[100][100];
		for (int i = 0; i < 5; i++) {
			int rgb = image.getRGB(i, 0);			// TODO Auto-generated catch block
			int red = (rgb & 0x00ff0000) >> 16;
			int green = (rgb & 0x0000ff00) >> 8;
			int blue = rgb & 0x000000ff;

			// add the colors on the png
			// image first line
			// by order: terrain, water, light, medium and high vegetation
			Color c = new Color(red, green, blue); 
			mapColors.add(c); 
		}
		
		int none = mapColors.get(0).getRGB();
		int water = mapColors.get(1).getRGB();
		int light = mapColors.get(2).getRGB();
		int medium = mapColors.get(3).getRGB();
		int high = mapColors.get(4).getRGB();

		for (int i = 0; i < 100; i++) {
			for (int j = 0; j < 100; j++) {
				// get image pixel at position (x,y)
				int clr = image.getRGB(i, j+1);

				if (clr == none) 		mat[i][j] = 0;
				else if (clr == water) 	mat[i][j] = 1;
				else if (clr == light)	mat[i][j] = 2;
				else if (clr == medium)	mat[i][j] = 3;
				else if (clr == high)	mat[i][j] = 4;
			}
		}
		return mat;
	}

	public void setFirebreak(boolean b) {
		firebreak = b;
	}
	
	public void setFirefighters(int n) {
		this.nFirefighters = n;
	}

	public void setSpaceSize(int spaceSize) {
		this.spaceSize = spaceSize;
	}

	public void setPositioning(int n) {
        this.iPositioning = n;
    }
	
	@Override
	public void setup() {
		try {
			map = importMap();
		} catch (IOException e) {
			System.err.println("erro a importar o mapa");
		}
		schedule = new Schedule();
		if (dsurf != null) {
			dsurf.dispose();
		}
		dsurf = new DisplaySurface(this, "RFS Display");
		registerDisplaySurface(" RFS Display", dsurf);
	}

	public void setWindSpeed(int windspeed) {
		this.windSpeed = windspeed;
	}	
}
